package com.basker.pisces.spring;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.util.Assert;

import com.basker.pisces.config.ApplicationConfig;
import com.basker.pisces.exception.ExceptionFactory;

/**
 * {@link ApplicationContext}的封装类，单例，可以快速通过该类访问springIOC容器里的对象。
 * <p>
 *
 * @author hangwen
 */
public class SpringApplicationContext implements ApplicationContextAware, ApplicationEventPublisher {

    private static SpringApplicationContext instance = null;

    private static AtomicBoolean started = new AtomicBoolean(false);
    private static Logger logger = LoggerFactory.getLogger(SpringApplicationContext.class);

    /**
     * 获取{@link ApplicationConfig}实例
     *
     * @return
     */
    public static ApplicationConfig getConfig() {
        return getInstance().getConfig0();
    }

    public static SpringApplicationContext getInstance() {
        Assert.notNull(instance, "SpringApplicationContext's instance has not be created!");
        return instance;
    }

    public static void start() {
        start(SpringContants.DEFAULT_SPRING_RESOUECE_LOCATIONS);
    }

    public static void start(String... springResourceLocations) {
        if (!started.compareAndSet(false, true)) {
            if (logger.isErrorEnabled()) {
                logger.error(SpringApplicationContext.class.getName() + ".start() has been invoked!");
            }

            return;
        }

        if (ArrayUtils.isEmpty(springResourceLocations)) {
            throw ExceptionFactory.createRuntimeException("no spring resouce location!");
        }

        GenericXmlApplicationContext context = new GenericXmlApplicationContext();
        context.load(springResourceLocations);
        context.refresh();

        Runtime.getRuntime().addShutdownHook(new Thread(() -> context.close()));
    }

    @Autowired
    private ApplicationConfig config;

    private ApplicationContext ctx;

    /**
     * 获取指定类型的bean
     *
     * @param beanType
     * @return 如果没有找到bean会抛出{@link NoSuchBeanDefinitionException}
     */
    public <TBean> TBean getBean(Class<TBean> beanType) throws NoSuchBeanDefinitionException {
        this.valid();

        return this.getBean(beanType, true);
    }

    /**
     * 获取指定类型的bean
     *
     * @param beanType
     * @param required 对于必要的bean,如果没有找到bean会抛出{@link NoSuchBeanDefinitionException}，反之返回null
     * @return
     */
    public <TBean> TBean getBean(Class<TBean> beanType, boolean required) {
        this.valid();

        try {
            return this.ctx.getBean(beanType);
        } catch (NoSuchBeanDefinitionException e) {
            if (required) {
                throw e;
            }

            return null;
        }
    }

    /**
     * 获取指定名称和类型的bean
     *
     * @param beanName
     * @param beanType
     * @return 如果没有找到bean会抛出{@link NoSuchBeanDefinitionException}
     */
    public <TBean> TBean getBean(String beanName, Class<TBean> beanType) {
        this.valid();

        return getBean(beanName, beanType, true);
    }

    /**
     * 获取指定名称和类型的bean
     *
     * @param beanName
     * @param beanType
     * @param required 对于必要的bean,如果没有找到bean会抛出{@link NoSuchBeanDefinitionException}，反之返回null
     * @return 如果没有找到bean会抛出{@link NoSuchBeanDefinitionException}
     */
    public <TBean> TBean getBean(String beanName, Class<TBean> beanType, boolean required) {
        this.valid();

        try {
            return this.ctx.getBean(beanName, beanType);
        } catch (NoSuchBeanDefinitionException e) {
            if (required) {
                throw e;
            }

            return null;
        }
    }

    /**
     * 获取指定类型的所有bean
     *
     * @param beanType
     * @return key是bean的名称，value即bean对象
     */
    public <T> Map<String, T> getBeanMap(Class<T> beanType) {
        this.valid();
        return this.getBeanMap(beanType, null);
    }

    /**
     * 获取指定类型的所有bean
     *
     * @param beanType
     * @param filter   针对bean名称的过滤器
     * @return key是bean的名称，value即bean对象
     */
    public <T> Map<String, T> getBeanMap(Class<T> beanType, Predicate<String> filter) {
        this.valid();

        String[] beanNames = this.ctx.getBeanNamesForType(beanType);
        Map<String, T> map = new HashMap<String, T>(beanNames.length, 1);

        for (String name : beanNames) {
            if (filter != null) {
                if (!filter.test(name)) {
                    continue;
                }
            }

            T bean = getBean(name, beanType);
            if (bean != null) {
                map.put(name, bean);
            }
        }

        return map;
    }

    /**
     * 获取指定类型的所有bean
     *
     * @param beanType
     * @param filter   针对bean对象的过滤器
     * @return key是bean的名称，value即bean对象
     */
    public <T> Map<String, T> getBeanMapByBeanFilter(Class<T> beanType, Predicate<T> filter) {
        this.valid();

        String[] beanNames = this.ctx.getBeanNamesForType(beanType);

        Map<String, T> map = new HashMap<String, T>(beanNames.length, 1);

        for (String name : beanNames) {
            T bean = getBean(name, beanType);

            if (bean != null) {
                if (filter != null) {
                    if (!filter.test(bean)) {
                        continue;
                    }
                }

                map.put(name, bean);
            }
        }

        return map;
    }

    /**
     * 获取指定类型的所有bean
     *
     * @param beanType
     * @return
     */
    public <T> List<T> getBeans(Class<T> beanType) {
        this.valid();
        return new ArrayList<T>(this.getBeanMap(beanType, null).values());
    }

    /**
     * 获取类型上含有指定注解的所有bean
     *
     * @param annotationType
     * @return
     */
    public List<?> getBeansForAnnatation(Class<? extends Annotation> annotationType) {
        this.valid();
        String[] beanNames = this.ctx.getBeanNamesForAnnotation(annotationType);
        return Stream.of(beanNames).map(name -> this.ctx.getBean(name)).collect(Collectors.toList());
    }

    /**
     * 返回属性"spring.application.name"的值
     *
     * @return
     */
    public String getName() {
        return this.config.getString("spring.application.name");
    }

    @Override
    public void publishEvent(Object event) {
        this.valid();
        this.ctx.publishEvent(event);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
        instance = this;
    }

    private ApplicationConfig getConfig0() {
        return config;
    }

    private void valid() {
        Assert.notNull(this.ctx, "SpringApplicationContext is not found!");
    }

}
